1. Getting Started with CadQuery
What is CadQuery?
CadQuery is a Python-based parametric CAD library that allows you to build 3D models using code. It's perfect for creating precise, reproducible designs for 3D printing, manufacturing, and engineering applications.
Installation
# Using pip
pip install cadquery
# Using conda (recommended)
conda install -c conda-forge -c cadquery cadquery=master
Your First CadQuery Script
import cadquery as cq
# Create a simple box
result = cq.Workplane("XY").box(10, 10, 5)
# Display or export
show_object(result) # In CQ-editor
Setting Up Your Environment
Option 1: CQ-Editor
Recommended for beginners
- Download from GitHub
- Built-in 3D viewer
- Code editor included
- Easy visualization
Option 2: Jupyter Notebook
from jupyter_cadquery import show
show(result)
2. Understanding Workplanes
Workplanes are the foundation of CadQuery. They define the 2D plane where you'll draw shapes that will later become 3D objects.
Basic Workplane Concepts
import cadquery as cq
# Creating workplanes on different faces
wp_xy = cq.Workplane("XY") # Top view (default)
wp_xz = cq.Workplane("XZ") # Front view
wp_yz = cq.Workplane("YZ") # Side view
Moving and Transforming Workplanes
# Move workplane along Z axis
result = (cq.Workplane("XY")
.center(5, 5) # Move origin to (5, 5)
.box(10, 10, 2)
)
# Create workplane at a specific height
result = (cq.Workplane("XY")
.workplane(offset=10) # 10 units above XY plane
.circle(5)
.extrude(3)
)
Working with Multiple Workplanes
# Select faces to create new workplanes
result = (cq.Workplane("XY")
.box(20, 20, 10)
.faces(">Z") # Select top face
.workplane()
.circle(5)
.cutThruAll() # Cut a hole through
)
3. Basic 2D Shapes
2D shapes are the building blocks that you'll extrude or revolve into 3D objects.
Rectangles and Boxes
# Center rectangle
rect = cq.Workplane("XY").rect(10, 20)
# Rectangle from corner
rect = cq.Workplane("XY").rect(10, 20, centered=False)
# Rounded rectangle
rounded = cq.Workplane("XY").rect(10, 20).fillet2D(2)
Circles and Ellipses
# Simple circle
circle = cq.Workplane("XY").circle(10)
# Ellipse
ellipse = cq.Workplane("XY").ellipse(15, 10)
# Multiple circles at different points
circles = (cq.Workplane("XY")
.pushPoints([(0,0), (10,10), (20,0)])
.circle(3)
)
Polygons
# Regular polygon (hexagon)
hexagon = cq.Workplane("XY").polygon(6, 10)
# Custom polygon from points
custom = (cq.Workplane("XY")
.moveTo(0, 0)
.lineTo(10, 0)
.lineTo(10, 10)
.lineTo(5, 15)
.lineTo(0, 10)
.close()
)
Splines and Complex Shapes
# Spline through points
spline = (cq.Workplane("XY")
.spline([(0, 0), (5, 5), (10, 3), (15, 8)])
.close()
)
4. 3D Operations: Extrude and Revolve
Extrusion
Extrusion extends a 2D shape along a perpendicular axis to create 3D geometry.
# Simple extrusion
box = cq.Workplane("XY").rect(10, 10).extrude(5)
# Extrusion with taper
tapered = cq.Workplane("XY").rect(10, 10).extrude(10, taper=15)
# Extrude both directions
result = (cq.Workplane("XY")
.circle(5)
.extrude(10, both=True)
)
Revolution
Revolution rotates a 2D profile around an axis to create rotationally symmetric parts.
# Basic revolve - create a sphere
sphere = (cq.Workplane("XZ")
.circle(10)
.revolve()
)
# Partial revolve - create a bowl
bowl = (cq.Workplane("XZ")
.moveTo(5, 0)
.lineTo(10, 0)
.lineTo(10, 5)
.lineTo(5, 8)
.close()
.revolve(180) # Revolve 180 degrees
)
# Create a vase with spline profile
vase = (cq.Workplane("XZ")
.moveTo(5, 0)
.spline([(5, 0), (7, 5), (6, 10), (8, 15)])
.lineTo(0, 15)
.lineTo(0, 0)
.close()
.revolve()
)
- Extrude: Best for prismatic shapes (boxes, channels, beams)
- Revolve: Perfect for circular parts (bottles, shafts, bowls)
- Taper: Creates sloped walls (useful for molding)
5. Boolean Operations
Boolean operations combine multiple shapes using union, subtraction, or intersection to create complex geometries.
Union (Combining Shapes)
# Combine two shapes - automatic union
result = (cq.Workplane("XY")
.box(20, 20, 5)
.faces(">Z").workplane()
.circle(5).extrude(10)
)
Subtraction (Cutting)
# Cut a cylinder through a box
result = (cq.Workplane("XY")
.box(20, 20, 10)
.faces(">Z").workplane()
.circle(5).cutThruAll()
)
# Cut to specific depth
result = (cq.Workplane("XY")
.box(20, 20, 10)
.faces(">Z").workplane()
.circle(5).cutBlind(-5) # Cut 5 units down
)
Intersection
# Create intersection of sphere and box
sphere = cq.Workplane("XY").sphere(10)
box = cq.Workplane("XY").box(15, 15, 15)
result = sphere.intersect(box)
Complex Boolean Example
# Create a bracket with multiple operations
bracket = (cq.Workplane("XY")
.box(50, 40, 10) # Main body
.faces(">Z").workplane()
.pushPoints([(-15, 0), (15, 0)])
.circle(5).cutThruAll() # Mounting holes
.faces(">Y").workplane()
.rect(30, 8).cutBlind(-15) # Side slot
)
6. Adding Details: Fillets and Chamfers
Fillets and chamfers are essential for creating professional-looking designs, improving part strength, and making objects safer to handle.
Understanding Fillets
Fillets are rounded edges that create smooth transitions between surfaces. They reduce stress concentrations and eliminate sharp edges.
# Basic fillet on edges
box = (cq.Workplane("XY")
.box(20, 20, 10)
.edges(">Z")
.fillet(3)
)
Edge Selection Syntax
"|Z"- Edges parallel to Z axis (vertical edges)">Z"- Edges on the top face (positive Z direction)"<Z"- Edges on the bottom face (negative Z direction)"|X"- Edges parallel to X axis"|Y"- Edges parallel to Y axis">X and |Z"- Combine selectors with AND logic
# Multiple fillet sizes on different edges
result = (cq.Workplane("XY")
.box(30, 20, 10)
.edges("|Z").fillet(2)
.edges(">Z").fillet(1)
.edges("<Z").fillet(0.5)
)
2D Fillets
# Fillet 2D shapes before extruding
profile = (cq.Workplane("XY")
.rect(30, 20)
.fillet2D(5) # Rounds corners in 2D
.extrude(10)
)
# Combine 2D and 3D fillets
result = (cq.Workplane("XY")
.rect(40, 30)
.fillet2D(8)
.extrude(15)
.edges("|Z").fillet(2)
)
Understanding Chamfers
Chamfers create angled edges. They're easier to manufacture and help with part assembly.
# Basic 45-degree chamfer
box = (cq.Workplane("XY")
.box(20, 20, 10)
.edges(">Z")
.chamfer(2)
)
# Asymmetric chamfer
result = (cq.Workplane("XY")
.box(30, 30, 15)
.edges("|Z")
.chamfer(3, 1.5) # Different lengths create angle
)
When to Use Fillets vs Chamfers
✨ Use Fillets
- Comfortable handling required
- Reducing stress concentrations
- Premium aesthetic appearance
- Improving fluid flow
- Consumer products
⚡ Use Chamfers
- Easing part assembly
- Safety edge removal
- Easier manufacturing
- Creating visual breaks
- Industrial applications
7. Working with Holes and Patterns
Creating Holes
# Simple through hole
result = (cq.Workplane("XY")
.box(20, 20, 5)
.faces(">Z").workplane()
.hole(6) # 6mm diameter
)
# Countersink hole (for flat head screws)
result = (cq.Workplane("XY")
.box(20, 20, 5)
.faces(">Z").workplane()
.cskHole(6, 12, 82) # hole dia, csink dia, angle
)
# Counterbore hole (for socket head screws)
result = (cq.Workplane("XY")
.box(20, 20, 5)
.faces(">Z").workplane()
.cboreHole(6, 10, 3) # hole dia, cbore dia, depth
)
Linear Patterns
# Rectangular array of holes
result = (cq.Workplane("XY")
.box(50, 50, 5)
.faces(">Z").workplane()
.rarray(10, 10, 4, 4) # xSpace, ySpace, xCount, yCount
.circle(2).cutThruAll()
)
# Linear pattern along one axis
result = (cq.Workplane("XY")
.box(50, 20, 5)
.faces(">Z").workplane()
.rarray(10, 1, 5, 1)
.circle(2).cutThruAll()
)
Circular Patterns
# Polar array (evenly spaced around circle)
result = (cq.Workplane("XY")
.circle(30).extrude(5)
.faces(">Z").workplane()
.polarArray(20, 0, 360, 8) # radius, start, angle, count
.circle(3).cutThruAll()
)
Custom Point Patterns
# Holes at specific coordinates
points = [(0, 0), (10, 10), (-10, 10), (10, -10), (-10, -10)]
result = (cq.Workplane("XY")
.box(40, 40, 5)
.faces(">Z").workplane()
.pushPoints(points)
.circle(3).cutThruAll()
)
8. Assemblies and Multiple Parts
Creating Simple Assemblies
from cadquery import Assembly
# Create individual parts
base = cq.Workplane("XY").box(50, 50, 10)
post = cq.Workplane("XY").circle(5).extrude(30)
# Create assembly
assy = (Assembly()
.add(base, name="base", color=cq.Color("gray"))
.add(post, name="post",
loc=cq.Location(cq.Vector(0, 0, 10)),
color=cq.Color("blue"))
)
Positioning Parts
# Using Location for precise positioning
part1 = cq.Workplane("XY").box(20, 20, 10)
part2 = cq.Workplane("XY").box(10, 10, 20)
assy = Assembly()
assy.add(part1, name="base")
assy.add(part2, name="tower",
loc=cq.Location(cq.Vector(15, 0, 5)))
Exporting Assemblies
# Save as STEP file (preserves assembly structure)
assy.save("assembly.step")
9. Parametric Design Techniques
Parametric design uses variables to create flexible, reusable models that can be easily modified.
Basic Parametric Model
# Define parameters
width = 50
height = 30
thickness = 5
hole_diameter = 8
# Create parametric plate
plate = (cq.Workplane("XY")
.box(width, height, thickness)
.faces(">Z").workplane()
.pushPoints([(-width/3, 0), (width/3, 0)])
.circle(hole_diameter/2).cutThruAll()
)
Using Functions for Reusability
def create_mounting_plate(width, height, thickness, hole_dia, spacing):
"""Create a parametric mounting plate"""
return (cq.Workplane("XY")
.box(width, height, thickness)
.faces(">Z").workplane()
.pushPoints([(-spacing/2, 0), (spacing/2, 0)])
.circle(hole_dia/2).cutThruAll()
.edges("|Z").fillet(2)
)
# Generate different sizes instantly
plate1 = create_mounting_plate(50, 30, 5, 6, 30)
plate2 = create_mounting_plate(80, 40, 8, 10, 60)
- Easy to create design variations
- Automatic updates when parameters change
- Reusable code for similar parts
- Perfect for product families
- Rapid design iteration
10. Exporting for 3D Printing
STL Export
import cadquery as cq
# Create your model
model = cq.Workplane("XY").box(20, 20, 10)
# Export as STL (standard for 3D printing)
cq.exporters.export(model, "model.stl")
# Higher resolution export
cq.exporters.export(model, "model_hires.stl", tolerance=0.01)
STEP Export
# STEP format preserves exact geometry for CAD
cq.exporters.export(model, "model.step")
3D Printing Best Practices
def printable_box(width, height, depth, wall=2):
"""Create a hollow box optimized for printing"""
return (cq.Workplane("XY")
.box(width, height, depth)
.shell(-wall) # Hollow with wall thickness
.edges("|Z").fillet(wall * 0.5)
)
print_ready = printable_box(50, 50, 30, wall=2)
cq.exporters.export(print_ready, "box.stl")